#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>

int main()
{
/*
    标准输出和标准错误的区别:
        当 ./myfile > log.txt 时, 只能将标准输出的内容重定向到 log.txt 中
        要想把标准错误的内容也重定向到 log.txt 中, 命令行输入 ./myfile > log.txt 2 > &1
        后面的 2 > &1, 可以理解为把文件描述符 1 的内容赋值给文件描述符 2, 即 2 号文件描述符也指向 log.txt

    const char* msg1 = "hello 标准输出!\n";
    write(1, msg1, strlen(msg1));

    const char* msg2 = "hello 标准错误!\n";
    write(2, msg2, strlen(msg2));
*/

/*-------------------------------------------------------------------------------------------------------------------*/

/*
    现象:
        我们上节课所演示的重定向代码, 当我们在最后加上了 close(fd) 时, log.txt 中什么数据都没有.

    close(1);
    int fd = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    printf("fd : %d\n", fd);
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    close(fd);

    背景知识:(图1)
        1. FILE 结构体中维护的有 C 语言的缓冲区, printf 等打印函数会先将数据放到该缓冲区中.
        2. 进程退出的时候会刷新 FILE 里面的数据到内核缓冲区
        3. 刷新策略:    
                    (1) 立即刷新(不缓冲)
                    (2) 行刷新(遇到 \n 刷新), 比如显示器
                    (3) 全缓冲(缓冲区满了刷新), 比如往磁盘文件中写入
        (上述,1,2对于内核缓冲区也适用)
    原因:
            由于我们关闭了 1 号文件描述符对应的文件, 及显示器, 所以 printf 和 fprintf 是向 log.txt 文件中打印;
            
            显示器文件的刷新策略是行刷新, log.txt 是普通文件, 刷新策略是全缓冲;
            
            所以, 当我们输出重定向到普通文件 log.txt 时, FILE 中的缓冲区要满了才会把数据刷新给内核的缓冲区.
            
            当没有 close(fd) 时, 当进程退出的时候, 会根据 log.txt 的 fd 把 FILE 中缓冲区的所有内容刷新到内核缓冲区,
        内核缓冲区再把数据刷新到 log.txt 中, 所以 log.txt 中有内容;
            
            当加上 close(fd) 后, 由于是全缓冲, 我们要输出的数据都还在 FILE 中的缓冲区里, 当执行到 close(fd) 时,
        fd 对应的文件关闭了, 此时即使进程退出会把缓冲区里的数据全部刷新到内核缓冲区中,
        但由于 C 缓冲区刷新数据到内核缓冲区需要文件的 fd, 此时 fd 失效,
        所以 C 缓冲区的数据不能刷新到内核缓冲区, 则更不能刷新到log.txt中.

    
    解决:
        close(fd) 之前将 C 缓冲区的数据进行刷新 -> fflush(stdout)
*/ 

/*  此时 log.txt 中可以看到内容.
    close(1);
    int fd = open("./log.txt", O_CREAT|O_WRONLY, 0644);
    printf("fd : %d\n", fd);
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    fprintf(stdout, "hello world!\n");
    
    fflush(stdout);

    close(fd);
*/

/*
    当我们命令行 ./myfile > log.txt 重定向时, log.txt 中只有 "hello 标准输出!".
    原因是我们 close(1) 后, printf 和 fprintf 输入到 C 缓冲区的内容无法刷新到 log.txt 中了;
    但由于 write 是系统调用, 其输出的内容直接到内核缓冲区, 所以 write 的内容能显示到 log.txt 中.
    (write 向内核缓冲区输入的内容是在进程退出时刷新到 log.txt 中的, 因为 log.txt对应的是全缓冲)

    const char* msg1 = "hello 标准输出!\n";
    const char* msg2 = "hello 标准错误!\n";

    write(1, msg1, strlen(msg1));
    write(2, msg2, strlen(msg2));
    
    printf("hello world!\n");
    fprintf(stdout, "hello fprintf!\n");

    close(1);
*/ 

/*-----------------------------------------------------------------------------------------------------------------------------*/

/*
    现象:
        当我们重定向时, log.txt 中 write 的内容只有一份, 而 printf 等 C 语言接口打印的内容有两份.

    原因:
        当重定向时, printf 等 C 语言接口会将数据输出到 FILE 中的缓冲区中.
        fork 之后, 父子进程共享一个 FILE 变量, 当父子进程其中有一个进行缓冲区刷新时,
        会发生写时拷贝, 拷贝出一个同样的 FILE 变量, 新的 FILE 中的缓冲区数据也要被刷新, 所以会有两份数据.
        而 write 的内容直接输出到内核缓冲区, 进程管不到, 所以只有一份.

    解决:
        fork 之前强制刷新

    //系统调用
    const char* msg = "hello 标准输出!\n";
    write(1, msg, strlen(msg));

    //C语言接口
    printf("Hello printf\n");
    fprintf(stdout, "hello fprintf\n");
    fputs("hello fputs\n", stdout);

    fflush(stdout);     //强制刷新

    fork();     // 缓冲区数据会写时拷贝
*/ 

/*--------------------------------------------------------------------------------------------------------------------------*/

/*
    总结:
        stdout, cin/cout, iostream, fstream 对应的结构体和类中一定维护这一块缓冲区.
*/

    return 0;
}
